/*
*  qqclient.c
*
*  QQ Client. 
*
*  Copyright (C) 2008  Huang Guan
*
*  2008-7-12 Created.
*  2008-10-26 TCP UDP Server Infos are loaded from configuration file.
*
*  Description: This file mainly includes the functions about 
*  loading configuration, connecting server, guard thread, basic interface
*
*/

#include <string.h>
#include <stdlib.h>

#ifdef _WINDOWS
#include <winsock.h>
#include <wininet.h>
#else
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#endif

#include "md5.hpp"

#include "debug.hpp"
#include "config.hpp"
#include "qqsocket.hpp"
#include "packetmgr.hpp"
#include "protocol.hpp"
#include "qqclient.hpp"
#include "qun.hpp"
#include "group.hpp"
#include "buddy.hpp"
#include "util.hpp"
#include "qqconn.hpp"

#ifdef min
#undef min
#endif

static void read_config( qqclient* qq )
{
	assert( g_conf );
	qqconn_load_serverlist();
	qq->log_packet = config_readint( g_conf, "QQPacketLog" );
	/*if( config_readstr( g_conf, "QQNetwork" ) ){
		if( stricmp( config_readstr( g_conf, "QQNetwork" ), "TCP" ) == 0 )
			qq->network = TCP;
		else if( stricmp( config_readstr( g_conf, "QQNetwork" ), "PROXY_HTTP" ) == 0 )
			qq->network = PROXY_HTTP;
		else
			qq->network = UDP;
	}*/
	qq->network = TCP;
	if( config_readstr( g_conf, "QQVerifyDir" ) )
		strncpy( qq->verify_dir, config_readstr( g_conf, "QQVerifyDir" ), PATH_LEN );
	if( qq->verify_dir == NULL )
		strcpy( qq->verify_dir, "./web/verify" );
	mkdir_recursive( qq->verify_dir );
}

int qqclient_create( qqclient* qq, uint num, char* pass )
{
	uchar md5_pass[16];
	//加密密码
	md5_state_t mst;
	md5_init( &mst );
	md5_append( &mst, (md5_byte_t*)pass, strlen(pass) );
	md5_finish( &mst, (md5_byte_t*)md5_pass );
	return qqclient_md5_create( qq, num, md5_pass );
}


static void delete_func(const void *p)
{
	delete[] p;
}

int qqclient_md5_create( qqclient* qq, uint num, uchar* md5_pass )
{
	md5_state_t mst;	
	qq->number = num;
	memcpy( qq->md5_pass1, md5_pass, 16 );
	//
	md5_init( &mst );
	md5_append( &mst, (md5_byte_t*)qq->md5_pass1, 16 );
	md5_finish( &mst, (md5_byte_t*)qq->md5_pass2 );
	qq->mode = QQ_ONLINE;
	qq->process = P_INIT;
	read_config( qq );
	qq->version = QQ_VERSION;
	//
	//list_create( &qq->buddy_list, MAX_BUDDY );
	//list_create( &qq->qun_list, MAX_QUN );
	//list_create( &qq->group_list, MAX_GROUP );

	//create self info
	qq->self = buddy_get( qq, qq->number, 1 );
	if( !qq->self ){
		DBG("[%d] Fatal error: qq->self == NULL", qq->number);
		return -1;
	}
	return 0;
}



/**
 * @param p 
 * @param v
 * @return 1,已超时。0,未超时。
 */
static int timeout_searcher(const void* p, const void* v) {
	//v = 当前时间- timeout
	//time_alive : 发送时间
	//所以就是：如果 当前时间- timeout>发送时间，那么超时。
	if (*(time_t*) v - ((qqpacket*) p)->time_alive > 0) {
		return 1;
	} else {
		return 0;
	}
}

extern  void check_ready_packets( qqclient* qq );
static int packetmgr_check_packet( qqclient* qq, int timeout )
{
	qqpacketmgr *mgr = &qq->packetmgr;
	qqpacket* p;
	const time_t timeout_time = time(NULL) - timeout;
	//when locked, cannot recv packet till unlock.
	while(true){
		{
			QMutexLocker ql(&mgr->sent_loop.mutex );
			p = (qqpacket*) loop_search_without_lock(&mgr->sent_loop, (void*) &timeout_time, timeout_searcher);
			if(p==NULL) break; //没有找到任何已经超时的包。
			//将已超时的包从等待ack的队列里删除。准备重新发送
			loop_remove_without_lock( &mgr->sent_loop, p,false );
		}
		if( p->send_times >= 10 ){
			//重新发送次数太多。
			MSG("[%u] Failed to send the packet. command: %x\n", qq->number, p->command );
			if( p->command == QQ_CMD_SEND_IM ){
				buddy_msg_callback( qq, 10000, time(NULL), "刚才某条消息发送失败。" );
			}
			//make an event to tell the program that we have a
			//problem.
			char event[64];
			qsnprintf( event,sizeof(event)-1, "sendfailed^$%d^$%d", p->command, p->seqno );
			qqclient_put_event( qq, event );
			delete_func( p );
			mgr->failed_packets ++;
			//To avoid too many failed packets, just shut it down.
			if( mgr->failed_packets > 5 || qq->process != P_LOGIN ){
				DBG("too many failed packets, so shutdown the connection");
				qqclient_set_process( qq, P_ERROR );
			}
		}else{
			//因为超时，所以尝试重发。
			DBG_QS(QObject::tr("[%1] resend packet cmd: %2,send_times=%3").arg(qq->number).arg(qqcmdToQString(p->command)).arg(p->send_times ));
			packetmgr_put_urge( qq, p, 1 );
		}		
	}
	check_ready_packets(qq);
	return 0;

}
#define INTERVAL 1
void qqclient_keepalive(qqclient* qq)
{
	const int counter = qq->keep_alive_counter ++;
	if( qq->process == P_LOGGING || qq->process == P_LOGIN || qq->process == P_VERIFYING ){
		packetmgr_check_packet( qq, 10 );
		if( qq->process == P_LOGIN ){
			//1次心跳/分钟
			if( counter % ( 1 *60*INTERVAL) == 0 ){
				prot_user_keep_alive( qq );
			}
#ifndef NO_BUDDY_INFO
			//10分钟刷新在线列表 QQ2009是5分钟刷新一次。
			if( counter % ( 10 *60*INTERVAL) == 0 ){
				prot_buddy_update_online( qq, 0 );
				qun_update_online_all( qq );
			}
#endif
			//30分钟刷新状态和刷新等级
			if( counter % ( 30 *60*INTERVAL) == 0 ){
				prot_user_change_status( qq );
				prot_user_get_level( qq );
			}
			//	//等待登录完毕
			if( qq->login_finish==0 ){
				if( loop_is_empty(&qq->packetmgr.ready_loop) && 
					loop_is_empty(&qq->packetmgr.sent_loop) ){
						qq->login_finish = 1;	//we can recv message now.
				}
			}
			qq->online_clock ++;
		}
	}
}

void MyKeepAliveThread::run(){	
	int sleepintv = 1000/INTERVAL;
	int counter = 0;
	DBG("keepalive thread started");
	while( this->qq->process != P_INIT ){
		counter ++;
		if( counter % INTERVAL == 0 ){
			qqclient_keepalive(this->qq);
		}
		this->sleep( 1);
	}
	DBG("end.");
}

void MyKeepAliveThread::OnTerminate()
{
	DBG("OnTerminate");
}
int qqclient_login( qqclient* qq )
{
	DBG("login");
	int ret;
	if( qq->process != P_INIT ){
		DBG("please logout first");
		return -1;
	}
	if( qq->login_finish == 2 ){
		qqclient_logout( qq );
	}
	qqclient_set_process( qq, P_LOGGING );
	srand( qq->number + time(NULL) );
	//start packetmgr
	packetmgr_start( qq );
	packetmgr_new_seqno( qq );
	qqconn_get_server( qq );
	ret = qqconn_connect( qq );
	if( ret < 0 ){
		qqclient_set_process( qq, P_ERROR );
		return ret;
	}
	//ok, already connected to the server
	if( qq->network == PROXY_HTTP ){
		qqconn_establish( qq );
	}else{
		//send touch packet
		prot_login_touch( qq );
	}
	//keep
	qq->keep_alive_counter = 0;

	qq->thread_keepalive=new MyKeepAliveThread(qq);	
	qq->thread_keepalive->start();
	return 0;
}

void qqclient_detach( qqclient* qq )
{
	if( qq->process == P_INIT )
		return;
	if( qq->process == P_LOGIN ){
		int i;
		for( i = 0; i<4; i++ )
			prot_login_logout( qq );
	}else{
		DBG("process = %d", qq->process );
	}
	qq->login_finish = 2;
	qqclient_set_process( qq, P_INIT );
	qqsocket_close( qq->http_sock );
	qqsocket_close( qq->socket );
}

//该函数需要等待延时，为了避免等待，可以预先执行detach函数
void qqclient_logout( qqclient* qq )
{
	if( qq->login_finish != 2 ){ 	//未执行过detach
		if( qq->process == P_INIT )
			return;
		qqclient_detach( qq );
	}
	qq->login_finish = 0;
	DBG("joining keepalive");
	qq->thread_keepalive->wait(10000);
	packetmgr_end( qq );
}


void qqclient_cleanup( qqclient* qq )
{
	if( qq->login_finish == 2 )
		qqclient_logout( qq );
	if( qq->process != P_INIT )
		qqclient_logout( qq );
	qun_member_cleanup( qq );
	{QMutexLocker myql_(&qq->buddy_list_lock);
	for(qqclient::buddy_list_type::iterator i=qq->buddy_list.begin();i!=qq->buddy_list.end();++i)
		delete *i;
	qq->buddy_list.clear();
	}
	{QMutexLocker myql_(&qq->qun_list_lock);
	for(qqclient::qun_list_type::iterator i=qq->qun_list.begin();i!=qq->qun_list.end();++i)
		delete *i;
	qq->qun_list.clear();}
	{QMutexLocker myql_(&qq->group_list_lock);
	for(qqclient::group_list_type::iterator i=qq->group_list.begin();i!=qq->group_list.end();++i)
		delete *i;	
	qq->group_list.clear();
	}
	
}

int qqclient_verify( qqclient* qq, const char* code )
{
	if( qq->login_finish == 1 ){
		qqclient_set_process( qq, P_LOGIN );	//原来漏了这个  20090709
		prot_user_request_token( qq, qq->data.operating_number, qq->data.operation, 1, code );
	}else{
		qqclient_set_process( qq, P_LOGGING );	//原来这个是P_LOGIN，错了。 20090709
		prot_login_request( qq, &qq->data.verify_token, code, 0 );
	}
	DBG("verify code: %x", code );
	return 0;
}

int qqclient_add( qqclient* qq, uint number, char* request_str )
{
	qqbuddy* b = buddy_get( qq, number, 0 );
	if( b && b->verify_flag == VF_OK )	{
		prot_buddy_verify_addbuddy( qq, 03, number );	//允许一个请求x
	}else{
		strncpy( qq->data.addbuddy_str, request_str, 50 );
		prot_buddy_request_addbuddy( qq, number );
	}
	return 0;
}

int qqclient_del( qqclient* qq, uint number )
{
	qq->data.operating_number = number;
	prot_user_request_token( qq, number, OP_DELBUDDY, 6, 0 );
	return 0;
}

int qqclient_wait( qqclient* qq, int sec )
{
	int i;
	//we don't want to cleanup the data while another thread is waiting here.

	if( !qq->event_list_lock.tryLock())
		return -1;	//busy?
	for( i=0; (sec==0 || i<sec) && qq->process!=P_INIT; i++ ){
		if( loop_is_empty(&qq->packetmgr.ready_loop) && loop_is_empty(&qq->packetmgr.sent_loop) )
		{
			qq->event_list_lock.unlock();
			return 0;
		}
		SLEEP(1);
	}
	qq->event_list_lock.unlock();
	return -1;
}

void qqclient_change_status( qqclient* qq, uchar mode )
{
	qq->mode = mode;
	if( qq->process == P_LOGIN ){
		prot_user_change_status( qq );
	}
}

// wait: <0   wait until event arrives
// wait: 0  don't need to wait, return directly if no event
// wait: n(n>0) wait n secondes.
// return: 1:ok  0:no event  -1:error
int qqclient_get_event( qqclient* qq, char* event, int size, int wait )
{
	char* buf;
	//we don't want to cleanup the data while another thread is waiting here.
	if( !qq->event_list_lock.tryLock())
		return -1;	//busy?
	for( ; ; ){
		if(  qq->process == P_INIT ){
			qq->event_list_lock.unlock();
			return -1;
		}
		if( !qq->event_loop.empty()){
			std::string backv=qq->event_loop.back();
			qq->event_loop.pop_back();			
			memcpy(event,backv.c_str(),std::min<size_t>(size-1,backv.length()));
			event[size-1]='\0';
			qq->event_list_lock.unlock();
			return 1;
		}		
		if( wait<0 || wait> 0 ){
			if( wait>0) wait--;
			USLEEP( 200 );
		}else{
			break;
		}
	}
	qq->event_list_lock.unlock();
	return 0;
}

int qqclient_put_event( qqclient* qq, char* event )
{		
	QMutexLocker(&qq->event_list_lock);
	qq->event_loop.push_back(event);
	return 0;
}



void qqclient_set_process( qqclient *qq, int process )
{
	qq->process = process;
	char event[24];
	qsnprintf( event,sizeof(event)-1, "process^$%d", process );
	qqclient_put_event( qq, event );
}
